#include <GL/gl.h>
#include <GL/glu.h>
#include <fstream>
#include <iostream>
#include <SFML/Graphics.hpp>
#include <SRL/Engine/Engine.h>
using namespace std;


void CameraEvent(srl::Camera* camera, sf::RenderWindow* App);

int main()
{
    // Make the window with SFML
    sf::RenderWindow App(sf::VideoMode(800, 600, 32), "SRL_Engine");
    App.PreserveOpenGLStates(true);
    if(!InitGLEXT()) printf("<Erreur> Impossible de charger les extensions openGL\n");
    
    // Some variables used for controle the camera
    float CurseurPosx = App.GetInput().GetMouseX();
    float CurseurPosy = App.GetInput().GetMouseY();
    
    try {
	
	// Make the scene with a camera
	srl::GraphScene Scene(800,600);
	srl::Camera* Camera = Scene.AddCamera("perso", true);
	srl::Camera Light(srl::Vector<float>(2.0, 0.5, 2.0));

	// Load texture and apply it to the skybox	
	srl::Texture tex;
	srl::Load("Data/Skybox WOTT.png", &tex);
	srl::SkyBox Sky(&tex);
	
	// FBOs
	srl::FrameBuffer Fbo(1024, 1024, 0);
	srl::FrameBuffer ShadowFBO(1024, 1024, 0);
	srl::Texture FboTex1, FboTex2;
	Fbo.LinkTexture(&FboTex1, 0);// Fbo.LinkTexture(&FboTex2, 1);
	Fbo.Init();
	
	// Create and generate a terrain
	srl::Texture tex1; srl::Load("Data/Grass.png", &tex1);
	srl::Texture tex2; srl::Load("Data/RockTextureBW.jpg", &tex2);
	srl::Texture texAlpha; srl::Load("Data/GrassAlpha.png", &texAlpha);
	
	srl::Texture cubeDiff; srl::Load("Data/diffuse.jpg", &cubeDiff);
	srl::Texture cubeNorm; srl::Load("Data/normal.jpg", &cubeNorm);
	srl::Texture cubeGloss; srl::Load("Data/gloss.jpg", &cubeGloss);
	srl::Texture cubeHeight; srl::Load("Data/height.jpg", &cubeHeight);
	
	srl::TerrainLOD Terrain;
	Terrain.Init();
	
	// Use some shaders
	srl::Shader FS(GL_FRAGMENT_SHADER_ARB);
	srl::Shader VS(GL_VERTEX_SHADER_ARB);
	
	// Off-rendering Shaders
	if(!FS.SourceAndCompile("Data/OffRendering.fs")) std::cout << "<Erreur> Impossible de compiler le Fragment Shader" << FS.Log() << std::endl;
	if(!VS.SourceAndCompile("Data/OffRendering.vs")) std::cout << "<Erreur> Impossible de compiler le Vertex Shader" << VS.Log() << std::endl;

	srl::Program FBOPrg(&VS, &FS);
	//if(!FBOPrg.IsLinked()) std::cout << "<Erreur> Impossible de lier les shader :" << FBOPrg.Log() << std::endl;
	
	// Shadow map Shaders
	/*if(!FS.SourceAndCompile("Data/ShadowRender.fs")) std::cout << "<Erreur> Impossible de compiler le Fragment Shader" << std::endl;
	if(!VS.SourceAndCompile("Data/ShadowRender.vs")) std::cout << "<Erreur> Impossible de compiler le Vertex Shader" << std::endl;
	
	srl::Program ShadowPrg(&VS, &FS);*/
	//if(!ShadowPrg.IsLinked()) std::cout << "<Erreur> Impossible de lier les shader :" << Shadow.Log() << std::endl;
	
	// Final Render Shaders
	
	if(!FS.SourceAndCompile("Data/FinalRender.fs")) std::cout << "<Erreur> Impossible de compiler le Fragment Shader" << std::endl;
	if(!VS.SourceAndCompile("Data/FinalRender.vs")) std::cout << "<Erreur> Impossible de compiler le Vertex Shader" << std::endl;
	
	srl::Program FinalPrg(&VS, &FS);
	//if(!FinalPrg.IsLinked()) std::cout << "<Erreur> Impossible de lier les shader :" << FinalPrg.Log() << std::endl;
	
    srl::ScreenMap ScreenMap(0,0,600,800);
    ScreenMap.LinkProgram(&FinalPrg);
	ScreenMap.LinkFBO(&Fbo);
	
    sf::Event Event;
    while (App.IsOpened()) {
        
        while (App.GetEvent(Event)) {
            if(Event.Type == sf::Event::Closed)
                App.Close();
                
            if(Event.Type == sf::Event::Resized)
                glViewport(0, 0, Event.Size.Width, Event.Size.Height);
                
            if(Event.Type == sf::Event::MouseMoved) {
                int decalX = Event.MouseMove.X - (int)CurseurPosx;
                int decalY = Event.MouseMove.Y - (int)CurseurPosy;
                Camera->TargetMove(decalX, decalY);
                
                CurseurPosx = App.GetInput().GetMouseX();
                CurseurPosy = App.GetInput().GetMouseY();
            }
            
            CameraEvent(Camera, &App);
        }
         
        /**** Draw elements of the scene ****/
        Fbo.Use();
        Scene.Init();
        FBOPrg.Use();
            FBOPrg.Uniform("srlViewPort", 800, 600);
            
            FBOPrg.Uniform("lightPosition", srl::Vector<float>(5.0, 0.2, 0.0, 1.0));
            FBOPrg.Uniform("Cam", Camera->GetPos());
            //srl::Log << "Cam : " << srl::Vector<float>(5.0, 1.0, 5.0, 1.0) - Camera->GetPos() << "\n";
            
            // Draw the skybox
            srl::Send( SRL_NO_LIGHT | SRL_DIFFUSE | SRL_NO_PARALLAX | SRL_NO_NORMAL | SRL_ONCE );
            FBOPrg.Uniform("srlTextureDiffuse", &tex,1);
            Sky.Draw(Camera);
            // Draw the sun
            srl::Send( SRL_NO_LIGHT | SRL_NO_DIFFUSE | SRL_COLOR );
            Sky.DrawSun(Camera);
            
            // Draw the terrain
            srl::Send( SRL_LIGHT | SRL_NO_COLOR );
            FBOPrg.Uniform("srlTextureType", 2);
            FBOPrg.Uniform("srlIsLight", 0);
            FBOPrg.Uniform("srlTexture1A", &texAlpha, 1);
            FBOPrg.Uniform("srlTexture1", &tex1, 2);
            FBOPrg.Uniform("srlTexture2", &tex2, 3);
            Terrain.Draw();
            
            // Draw TEH cube
            srl::Send( SRL_LIGHT | SRL_DIFFUSE | SRL_PARALLAX | SRL_REPEAT | SRL_NORMAL );
            FBOPrg.Uniform("srlTextureDiffuse", &cubeDiff, 1);
            FBOPrg.Uniform("srlTextureNormal", &cubeNorm, 2);
            FBOPrg.Uniform("srlTextureGloss", &cubeGloss, 3);
            FBOPrg.Uniform("srlTextureHeight", &cubeHeight, 4);
            Scene.Draw();
        srl::Program::DUse();
        Fbo.DUse();
        
        /**** Shadow Map ****/
        ShadowFBO.Use();
        
        Scene.Init();
        	
        	Camera->Perspective();
        	Camera->Look();
        	
            Terrain.Draw();
            Scene.Draw();
            
        srl::Program::DUse();
        ShadowFBO.DUse();
        
        /*** Final render ***/
        ScreenMap.Render();
        
        App.Display();
    }
    } catch ( srl::Exception &ex ) { std::cout << ex.GetString() << std::endl << std::endl; }
    
    return EXIT_SUCCESS;
}

void CameraEvent(srl::Camera* camera, sf::RenderWindow* App) {
    if(App->GetInput().IsKeyDown(sf::Key::Z))
        camera->Walk();
    if(App->GetInput().IsKeyDown(sf::Key::S))
        camera->Back();
    if(App->GetInput().IsKeyDown(sf::Key::Q))
        camera->Left();
    if(App->GetInput().IsKeyDown(sf::Key::D))
        camera->Right();
}
